;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 32-bit kernel startup code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

%include "asm.inc"

SECTION .text
;SEGMENT _TEXT USE32 CLASS=CODE
;BITS 32

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; entry point
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

GLOBAL entry

entry:
; check if data segment linked, located, and loaded properly
	mov eax,[ds_magic]
	cmp eax,DS_MAGIC
	je ds_ok

; display a blinking white-on-blue 'D' and freeze
	mov word [0B8000h],9F44h
	jmp short $
ds_ok:

; stop using bootloader GDT, and load new GDT
	lgdt [gdt_ptr]

	mov ax,LINEAR_DATA_SEL
	mov ds,ax
	mov es,ax
	mov ss,ax
	mov fs,ax
	mov gs,ax
	jmp LINEAR_CODE_SEL:sbat
sbat:

; zero the C language BSS
; 'bss' and 'end' are defined in the linker script file
EXTERN bss, end
	mov edi,bss
	mov ecx,end
	sub ecx,edi
	xor eax,eax
	rep stosb

	mov esp,stack

; set up interrupt handlers, then load IDT register
	mov ecx,(idt_end - idt) >> 3 ; number of exception handlers
	mov edi,idt
	mov esi,isr0


; Loads the IDT defined in '_idtp' into the processor.
; This is declared in C as 'extern void idt_load();'


do_idt:
	mov eax,esi			; EAX=offset of entry point
	mov [edi],ax			; set low 16 bits of gate offset
	shr eax,16
	mov [edi + 6],ax		; set high 16 bits of gate offset
	add edi,8			; 8 bytes/interrupt gate
	add esi,(isr1 - isr0)		; bytes/stub
	loop do_idt

	lidt [idt_ptr]
	

; GRUB 0.90 leaves the NT bit set in EFLAGS. The first IRET we attempt
; will cause a TSS-based task-switch, which will cause Exception 10.
; Let's prevent that:
	push dword 2
	popf

IMP main
	push ebx
	
	call main			; call C code
	jmp $				; freeze


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Multiboot header for GRUB bootloader. This must be in the first 8K
; of the kernel file. We use the aout kludge so it works with ELF,
; DJGPP COFF, Win32 PE, or other formats.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; these are in the linker script file
EXTERN code, bss, end

ALIGN 4
mboot:
	dd MULTIBOOT_HEADER_MAGIC
	dd MULTIBOOT_HEADER_FLAGS
	dd MULTIBOOT_CHECKSUM
; aout kludge. These must be PHYSICAL addresses
	dd mboot
	dd code
	dd bss
	dd end
	dd entry

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; interrupt/exception handlers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

IMP fault

; I shouldn't have to do this!
%macro PUSHB 1
	db 6Ah
	db %1
%endmacro

%macro INTR 1				; (byte offset from start of stub)
isr%1:
	push byte 0			; ( 0) fake error code
	PUSHB %1			; ( 2) exception number
	push gs				; ( 4) push segment registers
	push fs				; ( 6)
	push es				; ( 8)
	push ds				; ( 9)
	pusha				; (10) push GP registers
		mov ax,LINEAR_DATA_SEL	; (11) put known-good values...
		mov ds,eax		; (15) ...in segment registers
		mov es,eax		; (17)
		mov fs,eax		; (19)
		mov gs,eax		; (21)
		mov eax,esp		; (23)
		push eax		; (25) push pointer to regs_t
.1:
; setvect() changes the operand of the CALL instruction at run-time,
; so we need its location = 27 bytes from start of stub. We also want
; the CALL to use absolute addressing instead of EIP-relative, so:
			mov eax,fault	; (26)
			call eax	; (31)
			jmp all_ints	; (33)
%endmacro				; (38)

%macro INTR_EC 1
isr%1:
	nop				; error code already pushed
	nop				; nop+nop=same length as push byte
	PUSHB %1			; ( 2) exception number
	push gs				; ( 4) push segment registers
	push fs				; ( 6)
	push es				; ( 8)
	push ds				; ( 9)
	pusha				; (10) push GP registers
		mov ax,LINEAR_DATA_SEL	; (11) put known-good values...
		mov ds,eax		; (15) ...in segment registers
		mov es,eax		; (17)
		mov fs,eax		; (19)
		mov gs,eax		; (21)
		mov eax,esp		; (23)
		push eax		; (25) push pointer to regs_t
.1:
; setvect() changes the operand of the CALL instruction at run-time,
; so we need its location = 27 bytes from start of stub. We also want
; the CALL to use absolute addressing instead of EIP-relative, so:
			mov eax,fault	; (26)
			call eax	; (31)
			jmp all_ints	; (33)
%endmacro				; (38)

; the vector within the stub (operand of the CALL instruction)
; is at (isr0.1 - isr0 + 1)

all_ints:
		pop eax
	popa				; pop GP registers
	pop ds				; pop segment registers
	pop es
	pop fs
	pop gs
	add esp,8			; drop exception number and error code
	iret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name:			getvect
; action:		reads interrupt vector
; in:			[EBP + 12] = vector number
; out:			vector stored at address given by [EBP + 8]
; modifies:		EAX, EDX
; minimum CPU:		'386+
; notes:		C prototype:
;			typedef struct
;			{	unsigned access_byte, eip; } vector_t;
;			getvect(vector_t *v, unsigned vect_num);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

EXP getvect
	push ebp
		mov ebp,esp
		push esi
		push ebx
			mov esi,[ebp + 8]

; get access byte from IDT[i]
			xor ebx,ebx
			mov bl,[ebp + 12]
			shl ebx,3
			mov al,[idt + ebx + 5]
			mov [esi + 0],eax

; get handler address from stub
			mov eax,isr1
			sub eax,isr0	; assume stub size < 256 bytes
			mul byte [ebp + 12]
			mov ebx,eax
			add ebx,isr0
			mov eax,[ebx + (isr0.1 - isr0 + 1)]
			mov [esi + 4],eax
		pop ebx
		pop esi
	pop ebp
	ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; name:			setvect
; action:		writes interrupt vector
; in:			[EBP + 12] = vector number,
;			vector stored at address given by [EBP + 8]
; out:			(nothing)
; modifies:		EAX, EDX
; minimum CPU:		'386+
; notes:		C prototype:
;			typedef struct
;			{	unsigned access_byte, eip; } vector_t;
;			getvect(vector_t *v, unsigned vect_num);
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

EXP setvect
	push ebp
		mov ebp,esp
		push esi
		push ebx
			mov esi,[ebp + 8]

; store access byte in IDT[i]
			mov eax,[esi + 0]
			xor ebx,ebx
			mov bl,[ebp + 12]
			shl ebx,3
			mov [idt + ebx + 5],al

; store handler address in stub
			mov eax,isr1
			sub eax,isr0	; assume stub size < 256 bytes
			mul byte [ebp + 12]
			mov ebx,eax
			add ebx,isr0
			mov eax,[esi + 4]
			mov [ebx + (isr0.1 - isr0 + 1)],eax
		pop ebx
		pop esi
	pop ebp
	ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; interrupt/exception stubs
; *** CAUTION: these must be consecutive, and must all be the same size.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	INTR 0		; zero divide (fault)
	INTR 1		; debug/single step
	INTR 2		; non-maskable interrupt (trap)
	INTR 3		; INT3 (trap)
	INTR 4		; INTO (trap)
	INTR 5		; BOUND (fault)
	INTR 6		; invalid opcode (fault)
	INTR 7		; coprocessor not available (fault)
	INTR_EC 8	; double fault (abort w/ error code)
	INTR 9		; coproc segment overrun (abort; 386/486SX only)
	INTR_EC 0Ah	; bad TSS (fault w/ error code)
	INTR_EC 0Bh	; segment not present (fault w/ error code)
	INTR_EC 0Ch	; stack fault (fault w/ error code)
	INTR_EC 0Dh	; GPF (fault w/ error code)
	INTR_EC 0Eh	; page fault
	INTR 0Fh	; reserved
	INTR 10h	; FP exception/coprocessor error (trap)
	INTR 11h	; alignment check (trap; 486+ only)
	INTR 12h	; machine check (Pentium+ only)
	INTR 13h
	INTR 14h
	INTR 15h
	INTR 16h
	INTR 17h
	INTR 18h
	INTR 19h
	INTR 1Ah
	INTR 1Bh
	INTR 1Ch
	INTR 1Dh
	INTR 1Eh
	INTR 1Fh

; isr20 through isr2F are hardware interrupts. The 8259 programmable
; interrupt controller (PIC) chips must be reprogrammed to make these work.
	INTR 20h	; IRQ 0/timer interrupt
	INTR 21h	; IRQ 1/keyboard interrupt
	INTR 22h
	INTR 23h
	INTR 24h
	INTR 25h
	INTR 26h	; IRQ 6/floppy interrupt
	INTR 27h
	INTR 28h	; IRQ 8/real-time clock interrupt
	INTR 29h
	INTR 2Ah
	INTR 2Bh
	INTR 2Ch
	INTR 2Dh	; IRQ 13/math coprocessor interrupt
	INTR 2Eh	; IRQ 14/primary ATA ("IDE") drive interrupt
	INTR 2Fh	; IRQ 15/secondary ATA drive interrupt

; syscall software interrupt
	INTR 30h

; the other 207 vectors are undefined

%assign i 31h
%rep (0FFh - 30h)

	INTR i

%assign i (i + 1)
%endrep

; Procedure ReadSectors - Reads sectors from the disk.
;  Input: cx - Number of sectors; ax - Start position
;  Output: Loaded file into: es:bx (bx initial value must be set)


_ReadSectors:
.MAIN:                          ; Main Label
        mov di, 5               ; Loop 5 times max!!!
.SECTORLOOP:
        push    ebp             ; create stack frame
        mov     ebp, esp
        mov     cx, [ebp+8]    ; get num of sec from stack
        mov     ax, [ebp+12]    ; get input location from stack
        mov     bx, [ebp+16]    ; get location from stack
	    mov es,bx		
		mov     bx, [ebp+20]    ; get offset from stack
		

        push ax                 ; Save register values on the stack
        push bx
        push cx
        push bx
	; AX must have the LBA sector value. (Correct)
	call LBAtoCHS             ; Change the LBA addressing to CHS addressing
	; AX=Sector, CX=Cylinder, BX=Head
        ; The code to read a sector from the floppy drive
        mov ch, cl                      ; Cylinder to read
        mov cl, al                      ; Sector to read
        mov dh, bl                      ; Head to read
        mov dl, BYTE [BootDrv]          ; Drive to read (Could be setup before loop to optimise)
        mov ah, 02              	; BIOS read sector function
        mov al, 01              	; read one sector
        pop bx
        int 0x13                	; Make the BIOS call
	; Loop round stuff
        jnc .SUCCESS
        dec di                  ; Decrease the error counter
        pop cx                  ; Restore the register values
        pop bx
        pop ax
        jnz .SECTORLOOP         ; Try the command again incase the floppy drive is being annoying
        call ReadError          ; Call the error command in case all else fails
.SUCCESS
        pop cx                  ; Restore the register values
        pop bx
        pop ax
        add bx, WORD [BytesPerSector]   ; Queue next buffer (Adjust output location so as to not over write the same area again with the next set of data)
        inc ax                          ; Queue next sector (Start at the next sector along from last time)
        ; I think I may add a status bar thing also. A # for each sector loaded or something.
        ; Shouldn't a test for CX go in here???
        dec cx                          ; One less sector left to read
        jz .ENDREAD                     ; Jump to the end of the precedure
        jmp .MAIN                      ; Read next sector (Back to the start)
.ENDREAD:                       ; End of the read procedure

        ret                     ; Return to main program

; Procedure to change LBA address system into CHS address system
;  Description: Gets absolute values from logical values. (Logical = Consecutive sectors; Absolute = Sector:Track:Head)
;  Calculations done:
;   absolute sector = (LBA mod SPT) + 1
;   absolute head = (LBA / SPT) MOD Heads
;   absolute cylinder = (LBA / SPT) / Heads
;  Input: AX - LBA value (And the FAT table of course)
;  Output: AX - Sector; BX - Head; CX - Cylinder

LBAtoCHS:
 PUSH dx			; Save the value in dx
 XOR dx,dx			; Zero dx
 MOV bx, [SectorsPerTrack]	; Move into place STP (LBA all ready in place)
 DIV bx				; Make the divide (ax/bx -> ax,dx)
 inc dx				; Add one to the remainder (sector value)
 push dx			; Save the sector value on the stack

 XOR dx,dx			; Zero dx
 MOV bx, [NumHeads]		; Move NumHeads into place (NumTracks all ready in place)
 DIV bx				; Make the divide (ax/bx -> ax,dx)

 MOV cx,ax			; Move ax to cx (Cylinder)
 MOV bx,dx			; Move dx to bx (Head)
 POP ax				; Take the last value entered on the stack off.
 POP dx				; Restore dx, in case something was there to start with
 RET				; Return to the main function

; Message procedures

ReadError:

_root_size:
	xor ax, ax			; Zero registers
	xor cx, cx
        mov ax,[MaxRootEntries] 	; Move value to register to work on. ax=Arithmatic
        mov cx, 32      		; Move value to multiply into register
        mul cx          		; Multiply
        div WORD [BytesPerSector]    	; Divide
        mov [8000h], ax      	; Put the value into a nice storage area for a bit
        mov ax, [ReservedSectors]	
        mov [0x8000], ax		
		ret
reboot:		
ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; -*-*-*-*-*-*-*-*-*-*-*-*-*-* End of main program -*-*-*-*-*-*-*-*-*-*-*-*-
; ******************************* Procedures *******************************

; Print message procedure
;  Input: Msg to display should be in DS:SI (End string with 0)
;  Output: Message displayed on the screen

PrintMsg:
        mov ah, 0x0E    ; Teletype Mode
        mov bh, 0x00    ; Page Number
        mov bl, 0x07    ; Text attribute (Colour 07 = Black and White)
.nextchar:
        lodsb           ; Load [si] into al and increment si
        or al,al        ; Set the Zero Flag if al = 0
        jz .return      ; If the Zero Flag is set, jump to Break
        int 0x10        ; Call BIOS Video Function
        jmp .nextchar   ; Loop around to write next character
.return
        ret             ; Return

; Reboot procedure
;  Input: None
;  Output: Reboot/Shutdown



SECTION .data
;SEGMENT _DATA USE32 CLASS=DATA

ds_magic:
	dd DS_MAGIC

gdt:
; NULL descriptor
	dw 0		; limit 15:0
	dw 0		; base 15:0
	db 0		; base 23:16
	db 0		; type
	db 0		; limit 19:16, flags
	db 0		; base 31:24

; unused descriptor
	dw 0
	dw 0
	db 0
	db 0
	db 0
	db 0

LINEAR_DATA_SEL	equ	$-gdt
	dw 0FFFFh
	dw 0
	db 0
	db 92h		; present, ring 0, data, expand-up, writable
	db 0CFh		; page-granular (4 gig limit), 32-bit
	db 0

LINEAR_CODE_SEL	equ	$-gdt
	dw 0FFFFh
	dw 0
	db 0
	db 9Ah		; present,ring 0,code,non-conforming,readable
	db 0CFh		; page-granular (4 gig limit), 32-bit
	db 0
gdt_end:

gdt_ptr:
	dw gdt_end - gdt - 1
	dd gdt

; 256 ring 0 interrupt gates

idt:
%rep 256
	dw 0				; offset 15:0
	dw LINEAR_CODE_SEL		; selector
	db 0				; (always 0 for interrupt gates)
	db 8Eh				; present,ring 0,'386 interrupt gate
	dw 0				; offset 31:16
%endrep
idt_end:

idt_ptr:
	dw idt_end - idt - 1		; IDT limit
	dd idt				; linear adr of IDT


fat12_table:

        OEM_ID                  db      "DFOS    "      ; 8 char sys ID
        BytesPerSector          dw      512             ; Sector size in bytes
        SectorsPerCluster       db      1               ; Sector per cluster
        ReservedSectors         dw      4               ; Reserved sectors (This I think for this should become 2 for the boot strap, was 1)
        TotalFATs               db      2               ; Number of fats
        MaxRootEntries          dw      224             ; Root directory entries
        TotalSectorsSmall       dw      2880            ; Total Sectors
        MediaDescriptor         db      0F0h            ; Format ID (FAT12 ID number)
        SectorsPerFAT           dw      9               ; Sectors per FAT
        SectorsPerTrack         dw      18              ; Sectors per track
        NumHeads                dw      2               ; Number of heads (2 as double sided floppy)
        HiddenSectors           dd      0               ; Special hidden sectors 
        TotalSectorsLarge       dd      0               ; More sectors
        DriveNumber             db      0               ; Drive Number (Primary Floppy is normally 0)
        Flags                   db      0               ; Reserved
        Signature               db      41              ; Boot signature
        VolumeID                dd      435101793       ; Volume serial number
        VolumeLabel             db      "NO NAME    "   ; Volume label (11 bytes)
        SystemID                db      "FAT12   "      ; File system (8 bytes)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Numbers/Other data:
BootDrv         db      0       ; Used to store the bootup drive

; Messages:
Welcome: db "Force Boot Loader Ver 0.3 (thanks to DFOS)", 13, 10, 0
rebootMsg: db "Press any key to reboot"
ReadErrorMsg: db "Error Reading Sector (Possible cause is corruption, or program error)",13,10,0

; ************** End matter (needed only for boot loaders) *****************


dw 0xAA55               ; 0x55 and 0xAA are the last bytes

; Possible future option is to have the next stage included here.


SECTION .bss
;SEGMENT _BSS USE32 CLASS=BSS

	resd 1024
stack:
